package jumpingnotes.red5;

import org.red5.server.adapter.MultiThreadedApplicationAdapter;
import org.red5.server.api.IConnection;
import org.red5.server.api.service.IServiceCapableConnection;
import org.red5.server.api.IScope;

import jumpingnotes.model.*;
import jumpingnotes.storage.StorageService;
import jumpingnotes.codec.CodecService;
import jumpingnotes.codec.CodecTask;
import jumpingnotes.codec.CodecTaskListener;
import jumpingnotes.codec.CodecParams;
import jumpingnotes.dao.*;
import jumpingnotes.deploy.DeployService;
import jumpingnotes.deploy.DeployEventListener;
import jumpingnotes.deploy.DeployTask;

import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.UUID;
import java.lang.Integer;

public class Application extends MultiThreadedApplicationAdapter
implements CodecTaskListener, DeployEventListener, ResourceTypes {

	private ResourceDAO resourceDAO;
	private AudioDAO audioDAO;
	private BookDAO bookDAO;
	private DeployDAO deployDAO;
	private SurveyDAO surveyDAO;
	private CodecService codecService;
	private StorageService storageService;
	private DeployService deployService;
	
	public void setResourceDAO(ResourceDAO resourceDAO)
	{
		this.resourceDAO = resourceDAO;
	}
	
	public void setAudioDAO(AudioDAO audioDAO)
	{
		this.audioDAO = audioDAO;
	}
	
	public void setBookDAO(BookDAO bookDAO)
	{
		this.bookDAO = bookDAO;
	}
	
	public void setDeployDAO(DeployDAO deployDAO)
	{
		this.deployDAO = deployDAO;
	}
	
	public void setSurveyDAO(SurveyDAO surveyDAO)
	{
		this.surveyDAO = surveyDAO;
	}
	
	public void setCodecService(CodecService codecService) {
		this.codecService = codecService;
	}

	public void setStorageService(StorageService storageService) {
		this.storageService = storageService;
	}

	public void setDeployService(DeployService deployService) {
		this.deployService = deployService;
	}

	@Override
	public boolean appStart(IScope app) {
		codecService.addListener(this);
		deployService.addListener(this);
		return super.appStart(app);
	}

	@Override
	public void appStop(IScope app) {
		codecService.removeListener(this);
		super.appStop(app);
	}

	/**
	 * Return the book basic info.
	 * 
	 * @param params
	 * 			map including keys: "uuid"
	 * @return 0 - book exist, 304 - resource not exist
	 * 			result BookInfo	
	 * @throws ServiceException
	 */
	public Result getBookInfo(IConnection con, Map<String, String> params) throws Exception
	{
		try {
			Book book = (Book) resourceDAO.findByUuid(params.get("uuid"));
			if (book == null) {
				return new Result(ErrorType.RESOURCE_NOT_EXIST);
			}
			BookInfo bi = new BookInfo();
			bi.setName(book.getResourceName());
			bi.setAuthor(book.getAuthor());
			bi.setUuid(book.getResourceUuid());
			bi.setUploader(book.getUserId());
			bi.setBookAbstract(book.getBookAbstract());
			return new Result(bi);
		}
		catch (Exception e)
		{			
			e.printStackTrace();
		}		
		return new Result(ErrorType.SYSTEM_ERROR);
	}
	
	/**
	 * Return the audio basic info.
	 * 
	 * @param params
	 * 			map including keys: "uuid"
	 * @return 0 - audio exist, 304 - resource not exist
	 * 			result AudioInfo	
	 * @throws ServiceException
	 */
	public Result getAudioInfo(IConnection con, Map<String, String> params) throws Exception
	{
		try {
			Audio audio = (Audio) resourceDAO.findByUuid(params.get("uuid"));
			if (audio == null) {
				return new Result(ErrorType.RESOURCE_NOT_EXIST);
			}			
			AudioInfo ai = new AudioInfo();
			ai.setName(audio.getResourceName());
			ai.setUuid(audio.getResourceUuid());
			ai.setRecorder(audio.getUserId());
			ai.setBookUuid(audio.getBook().getResourceUuid());
			ai.setSyncUuid(audio.getSync().getResourceUuid());
			return new Result(ai);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}		
		return new Result(ErrorType.SYSTEM_ERROR);
	}

	/**
	 * Return the deploy info by reading.
	 * 
	 * @param params
	 * 			map including keys: "readingUuid"
	 * @return 0 - audio exist, 304 - resource not exist
	 * 			result List<DeployInfo>	
	 * @throws ServiceException
	 */
	public Result getDeployInfo(IConnection con, Map<String, String> params) throws Exception
	{
		try {
			String readingUuid = params.get("readingUuid").toString();
			List<Deploy> list = deployDAO.findByAudioUuid(readingUuid);
			ArrayList diList = new ArrayList();
			for (int i=0; i<list.size(); i++) {
				DeployInfo di = new DeployInfo();
				di.setUuid(list.get(i).getResourceUuid());
				di.setDeployType(list.get(i).getDeployType());
				di.setModel(list.get(i).getModel());
				di.setVendor(list.get(i).getVendor());
				diList.add(di);
			}		
			return new Result(diList.toArray());
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}		
		return new Result(ErrorType.SYSTEM_ERROR);
	}

	/**
	 * Search audio & book by keyword.
	 * 
	 * @param params
	 * 			map including keys: "keyword", "type", "index", "length"
	 * @return 0 - search success
	 * 			result AudioInfo or BookInfo	
	 * @throws ServiceException
	 */
	public Result search(IConnection con, Map<String, Object> params) throws Exception
	{
		try {
			String typeStr = params.get("type").toString();
			int type = Integer.parseInt(typeStr);
			String keywordStr = params.get("keyword").toString();
			
			if (type==ResourceTypes.TYPE_AUDIO) {
				List<Audio> list;
				if (keywordStr.equals("")) {
					list = audioDAO.findAllReading(
						Integer.parseInt(params.get("index").toString()),
						Integer.parseInt(params.get("length").toString()), 
						null
						);
				}
				else {
					list = audioDAO.findReadingByKeyword(
						keywordStr, 
						Integer.parseInt(params.get("index").toString()), 
						Integer.parseInt(params.get("length").toString()), 
						null
						);
				}
				ArrayList aiList = new ArrayList();
				for (int i=0; i<list.size(); i++) {
					Resource sync = (Resource) list.get(i).getSync();
					if (sync!=null) {
						AudioInfo ai = new AudioInfo();
						ai.setId(list.get(i).getResourceId());
						ai.setSyncUuid(sync.getResourceUuid());										
						ai.setName(list.get(i).getResourceName());
						ai.setUuid(list.get(i).getResourceUuid());
						ai.setDuration(list.get(i).getDuration());
						ai.setRecorder(list.get(i).getUserId());
						ai.setBookUuid(list.get(i).getBook().getResourceUuid());
						ai.setBookName(list.get(i).getBook().getResourceName());
						ai.setBookAuthor(list.get(i).getBook().getAuthor());	
						aiList.add(ai);
					}
				}				
				return new Result(aiList.toArray());
			}
			else if (type==ResourceTypes.TYPE_BOOK) {
				Sorter sorter = Sorter.desc("resourceId");
				List<Book> list;
				if (keywordStr.equals("")) {
					list = bookDAO.findAll(
						Integer.parseInt(params.get("index").toString()),
						Integer.parseInt(params.get("length").toString()), 
						sorter
						);
				}
				else {
    				list = bookDAO.findByKeyword(
    					keywordStr, 
    					Integer.parseInt(params.get("index").toString()), 
    					Integer.parseInt(params.get("length").toString()), 
    					sorter
						);
    				}
				ArrayList biList = new ArrayList();
				for (int i=0; i<list.size(); i++) {
					BookInfo bi = new BookInfo();
					bi.setName(list.get(i).getResourceName());
					bi.setAuthor(list.get(i).getAuthor());
					bi.setUuid(list.get(i).getResourceUuid());
					bi.setUploader(list.get(i).getUserId());
					bi.setBookAbstract(list.get(i).getBookAbstract());
					biList.add(bi);
				}				
				return new Result(biList.toArray());				
			}
			else {
				return new Result(ErrorType.RESOURCE_NOT_EXIST);				
			}

		}
		catch (Exception e)
		{
			e.printStackTrace();
		}		
		return new Result(ErrorType.SYSTEM_ERROR);
	}

	/**
	 * Return audio & book result count of  by keyword.
	 * 
	 * @param params
	 * 			map including keys: "keyword", "type"
	 * @return 0 - search success
	 * 			result number	
	 * @throws ServiceException
	 */
	public Result count(IConnection con, Map<String, Object> params) throws Exception
	{
		try {
			String typeStr = params.get("type").toString();
			int type = Integer.parseInt(typeStr);
			String keywordStr = params.get("keyword").toString();
			
			if (type==ResourceTypes.TYPE_AUDIO) {
				if (keywordStr.equals("")) {
					return new Result(audioDAO.getReadingCount());	
				} else {
					return new Result(audioDAO.getReadingCountByKeyword(keywordStr));
				}
			}	
			else if (type==ResourceTypes.TYPE_BOOK) {
				if (keywordStr.equals("")) {
					return new Result(bookDAO.getCount());	
				} else {
					return new Result(bookDAO.getCountByKeyword(keywordStr));
				}			
			}
			else {
				return new Result(ErrorType.RESOURCE_NOT_EXIST);
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}			
		return new Result(ErrorType.SYSTEM_ERROR);
	}
	
	/**
	 * Return a resource UUID.
	 * 
	 * @param params
	 * 			map without any keys
	 * @return 0 - generation success
	 * 			result UUID in String	
	 * @throws ServiceException
	 */
	public Result generateUuid(IConnection con, Map<String, String> params) throws Exception
	{
		try {
			String uuid = UUID.randomUUID().toString();
			return new Result(uuid);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return new Result(ErrorType.SYSTEM_ERROR);
	}

	public Result saveBook(IConnection conn, Map<String, Object> params) throws Exception {
		try {
			String uuid = (String) params.get("uuid");
			String bookName = (String) params.get("bookName");
			String bookAuthor = (String) params.get("bookAuthor");
			Book book = new Book();
			book.setResourceUuid(uuid);
			book.setResourceType(ResourceTypes.TYPE_BOOK);
			book.setResourceSubType(0);
			book.setResourceName(bookName);
			book.setAuthor(bookAuthor);
			bookDAO.save(book);		
			return new Result(ErrorType.NO_ERROR);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return new Result(ErrorType.SYSTEM_ERROR);
	}
	
	
	
	public Result saveAudio(IConnection conn, Map<String, Object> params) throws Exception {
		try {
			String tmpUuid = (String) params.get("uuid");
			String bookUuid = (String) params.get("bookUuid");
			if (tmpUuid == null || bookUuid == null) {
				return new Result(ErrorType.INVALID_PARAM);
			}
			Book book = (Book) resourceDAO.findByUuid(bookUuid);
			if (book == null) {
				return new Result(ErrorType.RESOURCE_NOT_EXIST);
			}
			String recorder = (String) params.get("recorder");
			int duration = Integer.parseInt(params.get("duration").toString());
			String persistUuid = UUID.randomUUID().toString();
			storageService.persistTmpObject(TYPE_AUDIO, tmpUuid, persistUuid);
			Audio audio = new Audio();
			audio.setResourceUuid(persistUuid);
			audio.setResourceName(persistUuid);
			audio.setBook(book);
			audio.setResourceType(TYPE_AUDIO);
			audio.setResourceSubType(SUBTYPE_AUDIO_MP3);
			audio.setUserId(recorder);
			audio.setDuration(duration);
			audioDAO.save(audio);
			return new Result(persistUuid);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return new Result(ErrorType.SYSTEM_ERROR);
	}
	
	public Result filterAudio(IConnection con, Map<String, Object> params) throws Exception {
		String inputFilename = (String) params.get("input");
		String outputFilename = (String) params.get("output");
		// TODO add additional parameters per CodecParams
		int bitRate = Integer.parseInt(params.get("bitRate").toString());
		int sampleRate = Integer.parseInt(params.get("sampleRate").toString());
		int volume = Integer.parseInt(params.get("volume").toString());
		int startTime = Integer.parseInt(params.get("startTime").toString());
		int duration = Integer.parseInt(params.get("duration").toString());
		CodecParams codecParams = new CodecParams(bitRate, sampleRate, volume, startTime, duration);

		CodecTask codecTask = new CodecTask(
				UUID.randomUUID().toString(),
				inputFilename,
				outputFilename,
				codecParams
				);
		codecTask.setContext(con);
		if (codecService.addTask(codecTask)) {
			return new Result(ErrorType.NO_ERROR);
		} else {
			return new Result(ErrorType.SYSTEM_ERROR);
		}
	}

	public Result deployReading(IConnection con, Map<String, Object> params) throws Exception {
		//String syncUuid = (String) params.get("syncUuid");
		String audioUuid = (String) params.get("audioUuid");
		String deployType = (String) params.get("deployType");
		String vendor = (String) params.get("vendor");
		String model = (String) params.get("model");
		
		Audio audio = (Audio) resourceDAO.findByUuid(audioUuid);
		//Resource sync = resourceDAO.findByUuid(syncUuid); 
		//assert(audio.getSync()==sync);
		String deployUuid = UUID.randomUUID().toString();
		Deploy deploy = new Deploy();
		deploy.setAudio(audio);
		deploy.setResourceUuid(deployUuid);
		deploy.setResourceName(deployUuid);
		deploy.setResourceType(ResourceTypes.TYPE_DEPLOY);
		deploy.setResourceSubType(ResourceTypes.SUBTYPE_RESERVED);
		// resource subtype will be filled by deploy service
		deploy.setDeployType(deployType);
		deploy.setVendor(vendor);
		deploy.setModel(model);
		
		int deployRst = deployService.deploy(deploy, con, null);
		if (deployRst >= 0) {
			deployDAO.save(deploy);
			return new Result(deployUuid);
		} else {
			return new Result(ErrorType.SYSTEM_ERROR);
		}
	}
	
	public Result saveSurvey(IConnection con, Map<String, Object> params) throws Exception {
		String surveyUuid = (String) params.get("uuid");
		String surveyStr = (String) params.get("content");
		String[] splits = surveyStr.split("&");
		for (int i=0; i<splits.length; i++) {
			String[] pair = splits[i].split("=");
			String name = pair[0];
			if (pair.length < 2)
				continue;
			String value = pair[1];
			Survey survey = new Survey();
			survey.setSurveyUuid(surveyUuid);
			survey.setSurveyName(name);
			survey.setSurveyValue(value);
			surveyDAO.save(survey);
		}		
		return new Result(ErrorType.NO_ERROR);
	}
	
	public void onTaskDone(CodecTask task) {
		if (task.getContext() instanceof IServiceCapableConnection) {
			IServiceCapableConnection conn =
				(IServiceCapableConnection) task.getContext();
			if (conn.isConnected()) {
				// TODO send error info if task fails
				((IServiceCapableConnection) conn).invoke("onFilterProgress", 
						new Object[]{task.getInputFilename(), new Integer(100)});
			}
		}
	}

	public void onTaskProgress(CodecTask task, int percentage) {
		if (task.getContext() instanceof IServiceCapableConnection) {
			IServiceCapableConnection conn =
				(IServiceCapableConnection) task.getContext();
			if (conn.isConnected()) {
				((IServiceCapableConnection) conn).invoke("onFilterProgress", 
						new Object[]{task.getInputFilename(), new Integer(percentage)});
			}
		}
	}

	public void onTaskStart(CodecTask task) {
		if (task.getContext() instanceof IServiceCapableConnection) {
			IServiceCapableConnection conn =
				(IServiceCapableConnection) task.getContext();
			if (conn.isConnected()) {
				((IServiceCapableConnection) conn).invoke("onFilterProgress", 
						new Object[]{task.getInputFilename(), new Integer(0)});
			}
		}
	}
	
	public void onDeployStart(DeployTask task) {
		//TODO
	}
	
	public void onDeployDone(DeployTask task) {
		if (task.getContext() instanceof IServiceCapableConnection) {
			IServiceCapableConnection conn =
				(IServiceCapableConnection) task.getContext();
			if (conn.isConnected()) {
				// TODO send error info if task fails
				((IServiceCapableConnection) conn).invoke("onDeployProgress", 
						new Object[]{task.getDeploy().getAudio().getResourceUuid(), new Integer(100)});
			}
		}
	}
	
	public void onDeployProgress(DeployTask task, int percentage) {
		//TODO
	}
}
